feat(pricing): add AWS Price List Service support#821
Conversation
|
For reviewer context — this is PR 1 of the 3-PR scope discussed in #791. Tracking issues for the remaining two:
Each PR is independently reviewable; this one does not depend on the others landing. |
|
Marked this ready for review. CI workflows are sitting in |
Implements the `AWSPriceListService.*` JSON 1.1 surface backed by a
bundled static snapshot on the classpath. Covers the five public
operations: `DescribeServices`, `GetAttributeValues`, `GetProducts`,
`ListPriceLists`, and `GetPriceListFileUrl`, with pagination (Base64
offset tokens) and the array-of-JSON-strings `PriceList` shape the
real AWS SDKs expect.
Rationale: Floci today cannot back FinOps tooling (cost detection,
budget guards, CUR parsers, etc.) because the billing APIs are
absent. Pricing is the stateless prerequisite for the Cost Explorer
and CUR services that follow — cost lines are synthesized as
`resource state x pricing snapshot`, so the snapshot data path is the
foundation.
Snapshot layout on the classpath (and mirrored on the filesystem
when `FLOCI_SERVICES_PRICING_SNAPSHOT_PATH` is set):
pricing-snapshots/services.json
pricing-snapshots/attribute-values/<ServiceCode>/<Attr>.json
pricing-snapshots/products/<ServiceCode>/<Region>.json
pricing-snapshots/price-lists/<ServiceCode>.json
The bundled snapshot is intentionally minimal — `AmazonEC2`,
`AmazonS3`, and `AWSLambda` in `us-east-1` — to exercise SDK parsing
and filter logic without bloating the image. Users needing broader
coverage point the override env var at a full snapshot generated
from the AWS Price List bulk API.
Wiring follows the existing JSON 1.1 stateless pattern (Textract,
Secrets Manager): descriptor in `ResolvedServiceCatalog`, handler
injected into `AwsJson11Controller`, service config on
`EmulatorConfig.ServicesConfig`. No new protocol, no changes to
existing services, no new runtime dependencies.
Tests: `PricingIntegrationTest` covers all five operations via
RestAssured JSON 1.1 wire-format assertions, including validation
errors, unknown-service errors, filter matching, and pagination
tokens (17 tests, all green).
Refs: https://github.com/orgs/floci-io/discussions/791
c4cbd9b to
9ed87c0
Compare
|
I tried to use this service from testcontainers and there seems to be an issue with the |
|
Thanks for the report @cfranzen — confirmed: the bundled Hotfix up at #836: registers the tree under |
|
@cfranzen — verified the fix locally against the native build (full log on #836). |
|
@cfranzen — thanks again for catching this. The original PR was working on the JVM image (and all the tests in Two GraalVM-only failures combined:
Fix in #836:
Verified locally with |
Implements the `AWSInsightsIndexService.*` JSON 1.1 surface backed by synthesizing cost and usage from Floci's existing resource state multiplied by the bundled AWS Pricing snapshot (floci-io#821). Covers the nine Cost Explorer operations the FinOps tooling ecosystem most commonly exercises: - `GetCostAndUsage` / `GetCostAndUsageWithResources` — full `TimePeriod`, `Granularity` (DAILY / MONTHLY / HOURLY), `Metrics` (UnblendedCost / BlendedCost / AmortizedCost / NetUnblendedCost / NetAmortizedCost / UsageQuantity / NormalizedUsageAmount), `GroupBy` (DIMENSION / TAG / COST_CATEGORY), and the recursive `Filter` expression tree (`And` / `Or` / `Not` / `Dimensions` / `Tags` / `CostCategories`). - `GetDimensionValues` — returns the dimension values present in the synthesized data; honors filters and search strings. - `GetTags` — returns tag keys / values across enumerated resources. - `GetReservationCoverage` / `GetReservationUtilization` — zero-totalled stubs so callers that hit these endpoints during smoke tests don't fail on `UnknownOperationException`. - `GetSavingsPlansCoverage` / `GetSavingsPlansUtilization` — same stubbed shape. - `GetCostCategories` — empty list (cost-category management is out-of-scope for this PR). `GROUP_BY=RECORD_TYPE` distinguishes `Usage`, `Credit`, `Tax`, `Refund`, `DiscountedUsage`, and the four `SavingsPlan*` variants. `Tax` / `Refund` / `DiscountedUsage` / `SavingsPlan*` are reserved for follow-up PRs; `Credit` is emitted when `FLOCI_SERVICES_CE_CREDIT_USD_MONTHLY > 0`, capped at the synthesized monthly usage so net cost never goes below zero. ## Architecture A new SPI lives in `core/common/`: public interface ResourceUsageEnumerator { Stream<UsageLine> enumerate(Instant start, Instant end, String region); } CDI auto-discovers all `@ApplicationScoped` beans implementing it. Cost Explorer iterates `Instance<ResourceUsageEnumerator>` per request, so adding a new Floci service with cost data is a matter of dropping a new enumerator bean next to the service — zero changes to `CostExplorerService`. The same enumerators will feed the upcoming CUR and BCM Data Exports Parquet writer (floci-io#823) without duplication. The bundled enumerators are: - `Ec2UsageEnumerator` — `BoxUsage:<instanceType>` * hours per running instance, priced from the Pricing snapshot. - `S3UsageEnumerator` — `TimedStorage-ByteHrs` * GB-month per bucket, priced from the snapshot. - `LambdaUsageEnumerator` — catalog-only line per function (zero quantity until invocation tracking lands in a follow-up). - `UnpricedServicesEnumerator` — emits zero-quantity catalog rows for the remaining ~30 Floci services so they appear in `GetDimensionValues SERVICE` without contributing billed cost. Each priced enumerator additionally emits a zero-quantity catalog line so the service shows up in `GetDimensionValues SERVICE` even when no resources exist. ## Tests `CostExplorerIntegrationTest` covers all nine operations via RestAssured wire-format assertions: granularity bucketing, filter-expression evaluation, group-by combinations, dimension and tag listings, RI / SP stubs, validation errors. 20 tests, all green. The full Floci test suite stays green with the additions (4030 tests run, 0 failures, 0 errors, excluding the pre-existing local-env flakes documented during PR floci-io#821). Refs: https://github.com/orgs/floci-io/discussions/791 Refs: floci-io#822
Implements the `AWSInsightsIndexService.*` JSON 1.1 surface backed by synthesizing cost and usage from Floci's existing resource state multiplied by the bundled AWS Pricing snapshot (#821). Covers the nine Cost Explorer operations the FinOps tooling ecosystem most commonly exercises: - `GetCostAndUsage` / `GetCostAndUsageWithResources` — full `TimePeriod`, `Granularity` (DAILY / MONTHLY / HOURLY), `Metrics` (UnblendedCost / BlendedCost / AmortizedCost / NetUnblendedCost / NetAmortizedCost / UsageQuantity / NormalizedUsageAmount), `GroupBy` (DIMENSION / TAG / COST_CATEGORY), and the recursive `Filter` expression tree (`And` / `Or` / `Not` / `Dimensions` / `Tags` / `CostCategories`). - `GetDimensionValues` — returns the dimension values present in the synthesized data; honors filters and search strings. - `GetTags` — returns tag keys / values across enumerated resources. - `GetReservationCoverage` / `GetReservationUtilization` — zero-totalled stubs so callers that hit these endpoints during smoke tests don't fail on `UnknownOperationException`. - `GetSavingsPlansCoverage` / `GetSavingsPlansUtilization` — same stubbed shape. - `GetCostCategories` — empty list (cost-category management is out-of-scope for this PR). `GROUP_BY=RECORD_TYPE` distinguishes `Usage`, `Credit`, `Tax`, `Refund`, `DiscountedUsage`, and the four `SavingsPlan*` variants. `Tax` / `Refund` / `DiscountedUsage` / `SavingsPlan*` are reserved for follow-up PRs; `Credit` is emitted when `FLOCI_SERVICES_CE_CREDIT_USD_MONTHLY > 0`, capped at the synthesized monthly usage so net cost never goes below zero. ## Architecture A new SPI lives in `core/common/`: public interface ResourceUsageEnumerator { Stream<UsageLine> enumerate(Instant start, Instant end, String region); } CDI auto-discovers all `@ApplicationScoped` beans implementing it. Cost Explorer iterates `Instance<ResourceUsageEnumerator>` per request, so adding a new Floci service with cost data is a matter of dropping a new enumerator bean next to the service — zero changes to `CostExplorerService`. The same enumerators will feed the upcoming CUR and BCM Data Exports Parquet writer (#823) without duplication. The bundled enumerators are: - `Ec2UsageEnumerator` — `BoxUsage:<instanceType>` * hours per running instance, priced from the Pricing snapshot. - `S3UsageEnumerator` — `TimedStorage-ByteHrs` * GB-month per bucket, priced from the snapshot. - `LambdaUsageEnumerator` — catalog-only line per function (zero quantity until invocation tracking lands in a follow-up). - `UnpricedServicesEnumerator` — emits zero-quantity catalog rows for the remaining ~30 Floci services so they appear in `GetDimensionValues SERVICE` without contributing billed cost. Each priced enumerator additionally emits a zero-quantity catalog line so the service shows up in `GetDimensionValues SERVICE` even when no resources exist. ## Tests `CostExplorerIntegrationTest` covers all nine operations via RestAssured wire-format assertions: granularity bucketing, filter-expression evaluation, group-by combinations, dimension and tag listings, RI / SP stubs, validation errors. 20 tests, all green. The full Floci test suite stays green with the additions (4030 tests run, 0 failures, 0 errors, excluding the pre-existing local-env flakes documented during PR #821). Refs: https://github.com/orgs/floci-io/discussions/791 Refs: #822
* fix(pricing): bundle snapshot resources into native image The bundled pricing-snapshots tree was missing from the GraalVM native-image build, so the standard Floci container (which ships the native binary) had no service codes available — the Pricing snapshot loader silently returned null because the JSON files weren't present at runtime. Two changes: 1. Register `pricing-snapshots/**` under `quarkus.native.resources.includes` so GraalVM bundles every snapshot file into the image. Quarkus's static analysis cannot detect resource paths constructed dynamically (the loader joins `pricing-snapshots/` with a per-call relative path), so an explicit include is required. 2. Read snapshot resources via the class's own `ClassLoader` rather than the thread context loader. In a native image the thread context loader can be the bootstrap loader, which sees no application resources, while the class's loader sees everything bundled into the image. Reported by @cfranzen against #821 — Pricing service returned no service codes when called via Testcontainers and `load()` produced no log output, indicating the snapshot files were absent rather than malformed. Refs: #821 (comment) * ci: retrigger after sdk-test-node flake on apigatewayv2 websocket disconnect
…ports Implements PR 3 of the 3-PR billing-API scope discussed in floci-io#791. Adds two service surfaces that share one underlying export pipeline: - `cur:*` — legacy Cost and Usage Reports management plane, target prefix `AWSOrigamiServiceGatewayService.*`. - `bcm-data-exports:*` — modern Billing and Cost Management Data Exports management plane, target prefix `AWSBillingAndCostManagementDataExports.*`. Both run as JSON 1.1 services dispatched through the existing `AwsJson11Controller`. ## Operations CUR (7): - `PutReportDefinition`, `ModifyReportDefinition`, `DescribeReportDefinitions`, `DeleteReportDefinition` - `TagResource` / `UntagResource` / `ListTagsForResource` (stubs) BCM Data Exports (7): - `CreateExport`, `GetExport`, `ListExports`, `UpdateExport`, `DeleteExport` - `ListExecutions`, `GetExecution` AWS-shaped error codes throughout: `DuplicateReportNameException`, `ReportLimitReachedException`, `ReportNotFoundException`, `ResourceNotFoundException`, `ValidationException`, `UnknownOperationException`. Storage keys are account-scoped (`<accountId>::<region>::<reportName>` for CUR; `<accountId>::<exportArn>` and `<accountId>::<exportArn>::<executionId>` for BCM) so both services participate in Floci's per-account isolation. ## Parquet emission The same `ResourceUsageEnumerator` SPI introduced in floci-io#832 (Cost Explorer) backs the Parquet artifact: 1. `EmissionEngine` collects `UsageLine` rows from every `@ApplicationScoped` enumerator bean. 2. `FocusRowProjector` projects them into FOCUS 1.2 / CUR 2.0 column shape, reusing floci-io#821's bundled Pricing snapshot for unit prices. 3. Rows are staged as newline-delimited JSON in `s3://floci-cur-staging/...` via Floci's S3 service — escape-safe for tags, descriptions, and resource identifiers. 4. The `floci-duck` sidecar runs `COPY (SELECT * FROM read_json_auto('s3://...')) TO 's3://...' (FORMAT PARQUET)` so Parquet writes happen inside DuckDB, not in the Java process. This avoids pulling Apache Parquet / Hadoop / Avro into Floci's classpath and keeps the native binary footprint unchanged. 5. Staging objects are removed in a best-effort `finally` block; each run carries a fresh `runId` (UUID) in both staging and destination keys so concurrent emissions cannot clobber each other. The emission engine is shared between CUR and BCM, but the management planes are otherwise independent — they each own their own validation, storage, and JSON dispatch. ## Run modes `FLOCI_SERVICES_CUR_EMIT_MODE` and `FLOCI_SERVICES_BCM_DATA_EXPORTS_EMIT_MODE`: - `synchronous` (default): emit on every report/export mutation - `daily`: emit once per 24h via a CUR-owned single-thread `ScheduledExecutorService` — separate from the EventBridge Scheduler dispatcher, so internal billing exports do not surface as user-visible Scheduler resources. - `off`: management plane only Emission failures never roll back the management mutation. Failures surface as `ReportStatus.LastStatus = ERROR` on CUR and `Execution.ExecutionStatus.StatusCode = DELIVERY_FAILURE` on BCM. ## Tests 42 new tests across 5 classes: - `CurIntegrationTest` (11) — full CRUD on report definitions, duplicate / not-found / validation paths - `BcmDataExportsIntegrationTest` (16) — full CRUD on exports + executions, all validation paths - `FocusRowProjectorTest` (9) — pure-unit projection: usage rows, USD-denominated record types (Credit / Refund / Tax), missing resource id, tags JSON, billing-period anchoring, region+account flow-through, rate cache reuse - `ParquetEmitterIntegrationTest` (3) — DuckDB readback, staging cleanup, concurrent run isolation - `CurEmissionIntegrationTest` (3) — end-to-end: PutReportDefinition -> emit -> Parquet readable through DuckDB; ReportStatus updates; CreateExport produces an execution record Full Floci suite stays green (4194/4194, excluding the pre-existing local-env flakes documented during PR floci-io#821 / floci-io#832 and the two duck-required tests added here that need the `floci-duck` sidecar and a fixed Floci HTTP port to verify cross-container S3 access). ## Documentation - `docs/services/cur.md` — operations, validation rules, emission pipeline, configuration, examples. - `docs/services/bcm-data-exports.md` — operations, validation, execution lifecycle, configuration, examples. - README service table updated with rows for both, plus a note that CUR / BCM Data Exports share one Parquet engine. Refs: https://github.com/orgs/floci-io/discussions/791 Refs: floci-io#823 Builds on: floci-io#821, floci-io#832
…ports Implements PR 3 of the 3-PR billing-API scope discussed in floci-io#791. Adds two service surfaces that share one underlying export pipeline: - `cur:*` — legacy Cost and Usage Reports management plane, target prefix `AWSOrigamiServiceGatewayService.*`. - `bcm-data-exports:*` — modern Billing and Cost Management Data Exports management plane, target prefix `AWSBillingAndCostManagementDataExports.*`. Both run as JSON 1.1 services dispatched through the existing `AwsJson11Controller`. ## Operations CUR (7): - `PutReportDefinition`, `ModifyReportDefinition`, `DescribeReportDefinitions`, `DeleteReportDefinition` - `TagResource` / `UntagResource` / `ListTagsForResource` (stubs) BCM Data Exports (7): - `CreateExport`, `GetExport`, `ListExports`, `UpdateExport`, `DeleteExport` - `ListExecutions`, `GetExecution` AWS-shaped error codes throughout: `DuplicateReportNameException`, `ReportLimitReachedException`, `ReportNotFoundException`, `ResourceNotFoundException`, `ValidationException`, `UnknownOperationException`. Storage keys are account-scoped (`<accountId>::<region>::<reportName>` for CUR; `<accountId>::<exportArn>` and `<accountId>::<exportArn>::<executionId>` for BCM) so both services participate in Floci's per-account isolation. ## Parquet emission The same `ResourceUsageEnumerator` SPI introduced in floci-io#832 (Cost Explorer) backs the Parquet artifact: 1. `EmissionEngine` collects `UsageLine` rows from every `@ApplicationScoped` enumerator bean. 2. `FocusRowProjector` projects them into FOCUS 1.2 / CUR 2.0 column shape, reusing floci-io#821's bundled Pricing snapshot for unit prices. 3. Rows are staged as newline-delimited JSON in `s3://floci-cur-staging/...` via Floci's S3 service — escape-safe for tags, descriptions, and resource identifiers. 4. The `floci-duck` sidecar runs `COPY (SELECT * FROM read_json_auto('s3://...')) TO 's3://...' (FORMAT PARQUET)` so Parquet writes happen inside DuckDB, not in the Java process. This avoids pulling Apache Parquet / Hadoop / Avro into Floci's classpath and keeps the native binary footprint unchanged. 5. Staging objects are removed in a best-effort `finally` block; each run carries a fresh `runId` (UUID) in both staging and destination keys so concurrent emissions cannot clobber each other. The emission engine is shared between CUR and BCM, but the management planes are otherwise independent — they each own their own validation, storage, and JSON dispatch. ## Run modes `FLOCI_SERVICES_CUR_EMIT_MODE` and `FLOCI_SERVICES_BCM_DATA_EXPORTS_EMIT_MODE`: - `synchronous` (default): emit on every report/export mutation - `daily`: emit once per 24h via a CUR-owned single-thread `ScheduledExecutorService` — separate from the EventBridge Scheduler dispatcher, so internal billing exports do not surface as user-visible Scheduler resources. - `off`: management plane only Emission failures never roll back the management mutation. Failures surface as `ReportStatus.LastStatus = ERROR` on CUR and `Execution.ExecutionStatus.StatusCode = DELIVERY_FAILURE` on BCM. ## Tests 42 new tests across 5 classes: - `CurIntegrationTest` (11) — full CRUD on report definitions, duplicate / not-found / validation paths - `BcmDataExportsIntegrationTest` (16) — full CRUD on exports + executions, all validation paths - `FocusRowProjectorTest` (9) — pure-unit projection: usage rows, USD-denominated record types (Credit / Refund / Tax), missing resource id, tags JSON, billing-period anchoring, region+account flow-through, rate cache reuse - `ParquetEmitterIntegrationTest` (3) — DuckDB readback, staging cleanup, concurrent run isolation - `CurEmissionIntegrationTest` (3) — end-to-end: PutReportDefinition -> emit -> Parquet readable through DuckDB; ReportStatus updates; CreateExport produces an execution record Full Floci suite stays green (4194/4194, excluding the pre-existing local-env flakes documented during PR floci-io#821 / floci-io#832 and the two duck-required tests added here that need the `floci-duck` sidecar and a fixed Floci HTTP port to verify cross-container S3 access). ## Documentation - `docs/services/cur.md` — operations, validation rules, emission pipeline, configuration, examples. - `docs/services/bcm-data-exports.md` — operations, validation, execution lifecycle, configuration, examples. - README service table updated with rows for both, plus a note that CUR / BCM Data Exports share one Parquet engine. Refs: https://github.com/orgs/floci-io/discussions/791 Refs: floci-io#823 Builds on: floci-io#821, floci-io#832
…ports (#850) Implements PR 3 of the 3-PR billing-API scope discussed in #791. Adds two service surfaces that share one underlying export pipeline: - `cur:*` — legacy Cost and Usage Reports management plane, target prefix `AWSOrigamiServiceGatewayService.*`. - `bcm-data-exports:*` — modern Billing and Cost Management Data Exports management plane, target prefix `AWSBillingAndCostManagementDataExports.*`. Both run as JSON 1.1 services dispatched through the existing `AwsJson11Controller`. ## Operations CUR (7): - `PutReportDefinition`, `ModifyReportDefinition`, `DescribeReportDefinitions`, `DeleteReportDefinition` - `TagResource` / `UntagResource` / `ListTagsForResource` (stubs) BCM Data Exports (7): - `CreateExport`, `GetExport`, `ListExports`, `UpdateExport`, `DeleteExport` - `ListExecutions`, `GetExecution` AWS-shaped error codes throughout: `DuplicateReportNameException`, `ReportLimitReachedException`, `ReportNotFoundException`, `ResourceNotFoundException`, `ValidationException`, `UnknownOperationException`. Storage keys are account-scoped (`<accountId>::<region>::<reportName>` for CUR; `<accountId>::<exportArn>` and `<accountId>::<exportArn>::<executionId>` for BCM) so both services participate in Floci's per-account isolation. ## Parquet emission The same `ResourceUsageEnumerator` SPI introduced in #832 (Cost Explorer) backs the Parquet artifact: 1. `EmissionEngine` collects `UsageLine` rows from every `@ApplicationScoped` enumerator bean. 2. `FocusRowProjector` projects them into FOCUS 1.2 / CUR 2.0 column shape, reusing #821's bundled Pricing snapshot for unit prices. 3. Rows are staged as newline-delimited JSON in `s3://floci-cur-staging/...` via Floci's S3 service — escape-safe for tags, descriptions, and resource identifiers. 4. The `floci-duck` sidecar runs `COPY (SELECT * FROM read_json_auto('s3://...')) TO 's3://...' (FORMAT PARQUET)` so Parquet writes happen inside DuckDB, not in the Java process. This avoids pulling Apache Parquet / Hadoop / Avro into Floci's classpath and keeps the native binary footprint unchanged. 5. Staging objects are removed in a best-effort `finally` block; each run carries a fresh `runId` (UUID) in both staging and destination keys so concurrent emissions cannot clobber each other. The emission engine is shared between CUR and BCM, but the management planes are otherwise independent — they each own their own validation, storage, and JSON dispatch. ## Run modes `FLOCI_SERVICES_CUR_EMIT_MODE` and `FLOCI_SERVICES_BCM_DATA_EXPORTS_EMIT_MODE`: - `synchronous` (default): emit on every report/export mutation - `daily`: emit once per 24h via a CUR-owned single-thread `ScheduledExecutorService` — separate from the EventBridge Scheduler dispatcher, so internal billing exports do not surface as user-visible Scheduler resources. - `off`: management plane only Emission failures never roll back the management mutation. Failures surface as `ReportStatus.LastStatus = ERROR` on CUR and `Execution.ExecutionStatus.StatusCode = DELIVERY_FAILURE` on BCM. ## Tests 42 new tests across 5 classes: - `CurIntegrationTest` (11) — full CRUD on report definitions, duplicate / not-found / validation paths - `BcmDataExportsIntegrationTest` (16) — full CRUD on exports + executions, all validation paths - `FocusRowProjectorTest` (9) — pure-unit projection: usage rows, USD-denominated record types (Credit / Refund / Tax), missing resource id, tags JSON, billing-period anchoring, region+account flow-through, rate cache reuse - `ParquetEmitterIntegrationTest` (3) — DuckDB readback, staging cleanup, concurrent run isolation - `CurEmissionIntegrationTest` (3) — end-to-end: PutReportDefinition -> emit -> Parquet readable through DuckDB; ReportStatus updates; CreateExport produces an execution record Full Floci suite stays green (4194/4194, excluding the pre-existing local-env flakes documented during PR #821 / #832 and the two duck-required tests added here that need the `floci-duck` sidecar and a fixed Floci HTTP port to verify cross-container S3 access). ## Documentation - `docs/services/cur.md` — operations, validation rules, emission pipeline, configuration, examples. - `docs/services/bcm-data-exports.md` — operations, validation, execution lifecycle, configuration, examples. - README service table updated with rows for both, plus a note that CUR / BCM Data Exports share one Parquet engine. Refs: https://github.com/orgs/floci-io/discussions/791 Refs: #823 Builds on: #821, #832
Implements the `AWSPriceListService.*` JSON 1.1 surface backed by a
bundled static snapshot on the classpath. Covers the five public
operations: `DescribeServices`, `GetAttributeValues`, `GetProducts`,
`ListPriceLists`, and `GetPriceListFileUrl`, with pagination (Base64
offset tokens) and the array-of-JSON-strings `PriceList` shape the
real AWS SDKs expect.
Rationale: Floci today cannot back FinOps tooling (cost detection,
budget guards, CUR parsers, etc.) because the billing APIs are
absent. Pricing is the stateless prerequisite for the Cost Explorer
and CUR services that follow — cost lines are synthesized as
`resource state x pricing snapshot`, so the snapshot data path is the
foundation.
Snapshot layout on the classpath (and mirrored on the filesystem
when `FLOCI_SERVICES_PRICING_SNAPSHOT_PATH` is set):
pricing-snapshots/services.json
pricing-snapshots/attribute-values/<ServiceCode>/<Attr>.json
pricing-snapshots/products/<ServiceCode>/<Region>.json
pricing-snapshots/price-lists/<ServiceCode>.json
The bundled snapshot is intentionally minimal — `AmazonEC2`,
`AmazonS3`, and `AWSLambda` in `us-east-1` — to exercise SDK parsing
and filter logic without bloating the image. Users needing broader
coverage point the override env var at a full snapshot generated
from the AWS Price List bulk API.
Wiring follows the existing JSON 1.1 stateless pattern (Textract,
Secrets Manager): descriptor in `ResolvedServiceCatalog`, handler
injected into `AwsJson11Controller`, service config on
`EmulatorConfig.ServicesConfig`. No new protocol, no changes to
existing services, no new runtime dependencies.
Tests: `PricingIntegrationTest` covers all five operations via
RestAssured JSON 1.1 wire-format assertions, including validation
errors, unknown-service errors, filter matching, and pagination
tokens (17 tests, all green).
Refs: https://github.com/orgs/floci-io/discussions/791
Implements the `AWSInsightsIndexService.*` JSON 1.1 surface backed by synthesizing cost and usage from Floci's existing resource state multiplied by the bundled AWS Pricing snapshot (floci-io#821). Covers the nine Cost Explorer operations the FinOps tooling ecosystem most commonly exercises: - `GetCostAndUsage` / `GetCostAndUsageWithResources` — full `TimePeriod`, `Granularity` (DAILY / MONTHLY / HOURLY), `Metrics` (UnblendedCost / BlendedCost / AmortizedCost / NetUnblendedCost / NetAmortizedCost / UsageQuantity / NormalizedUsageAmount), `GroupBy` (DIMENSION / TAG / COST_CATEGORY), and the recursive `Filter` expression tree (`And` / `Or` / `Not` / `Dimensions` / `Tags` / `CostCategories`). - `GetDimensionValues` — returns the dimension values present in the synthesized data; honors filters and search strings. - `GetTags` — returns tag keys / values across enumerated resources. - `GetReservationCoverage` / `GetReservationUtilization` — zero-totalled stubs so callers that hit these endpoints during smoke tests don't fail on `UnknownOperationException`. - `GetSavingsPlansCoverage` / `GetSavingsPlansUtilization` — same stubbed shape. - `GetCostCategories` — empty list (cost-category management is out-of-scope for this PR). `GROUP_BY=RECORD_TYPE` distinguishes `Usage`, `Credit`, `Tax`, `Refund`, `DiscountedUsage`, and the four `SavingsPlan*` variants. `Tax` / `Refund` / `DiscountedUsage` / `SavingsPlan*` are reserved for follow-up PRs; `Credit` is emitted when `FLOCI_SERVICES_CE_CREDIT_USD_MONTHLY > 0`, capped at the synthesized monthly usage so net cost never goes below zero. ## Architecture A new SPI lives in `core/common/`: public interface ResourceUsageEnumerator { Stream<UsageLine> enumerate(Instant start, Instant end, String region); } CDI auto-discovers all `@ApplicationScoped` beans implementing it. Cost Explorer iterates `Instance<ResourceUsageEnumerator>` per request, so adding a new Floci service with cost data is a matter of dropping a new enumerator bean next to the service — zero changes to `CostExplorerService`. The same enumerators will feed the upcoming CUR and BCM Data Exports Parquet writer (floci-io#823) without duplication. The bundled enumerators are: - `Ec2UsageEnumerator` — `BoxUsage:<instanceType>` * hours per running instance, priced from the Pricing snapshot. - `S3UsageEnumerator` — `TimedStorage-ByteHrs` * GB-month per bucket, priced from the snapshot. - `LambdaUsageEnumerator` — catalog-only line per function (zero quantity until invocation tracking lands in a follow-up). - `UnpricedServicesEnumerator` — emits zero-quantity catalog rows for the remaining ~30 Floci services so they appear in `GetDimensionValues SERVICE` without contributing billed cost. Each priced enumerator additionally emits a zero-quantity catalog line so the service shows up in `GetDimensionValues SERVICE` even when no resources exist. ## Tests `CostExplorerIntegrationTest` covers all nine operations via RestAssured wire-format assertions: granularity bucketing, filter-expression evaluation, group-by combinations, dimension and tag listings, RI / SP stubs, validation errors. 20 tests, all green. The full Floci test suite stays green with the additions (4030 tests run, 0 failures, 0 errors, excluding the pre-existing local-env flakes documented during PR floci-io#821). Refs: https://github.com/orgs/floci-io/discussions/791 Refs: floci-io#822
* fix(pricing): bundle snapshot resources into native image The bundled pricing-snapshots tree was missing from the GraalVM native-image build, so the standard Floci container (which ships the native binary) had no service codes available — the Pricing snapshot loader silently returned null because the JSON files weren't present at runtime. Two changes: 1. Register `pricing-snapshots/**` under `quarkus.native.resources.includes` so GraalVM bundles every snapshot file into the image. Quarkus's static analysis cannot detect resource paths constructed dynamically (the loader joins `pricing-snapshots/` with a per-call relative path), so an explicit include is required. 2. Read snapshot resources via the class's own `ClassLoader` rather than the thread context loader. In a native image the thread context loader can be the bootstrap loader, which sees no application resources, while the class's loader sees everything bundled into the image. Reported by @cfranzen against floci-io#821 — Pricing service returned no service codes when called via Testcontainers and `load()` produced no log output, indicating the snapshot files were absent rather than malformed. Refs: floci-io#821 (comment) * ci: retrigger after sdk-test-node flake on apigatewayv2 websocket disconnect
Summary
Adds the AWS Price List Service (
pricing:*/AWSPriceListService.*) emulation — the stateless prerequisite for the Cost Explorer and CUR services proposed in #791.DescribeServices,GetAttributeValues,GetProducts,ListPriceLists,GetPriceListFileUrlAwsJson11ControllerdispatchFLOCI_SERVICES_PRICING_SNAPSHOT_PATHfor custom datasetsPriceListreturned as array-of-JSON-strings — matches real wire format so SDKs parse offers directlyWhy
Per scope discussion in #791, this is PR 1 of 3. FinOps tooling (cost detection, budget guards, CUR parsers, anomaly detectors) can't currently run against Floci because the billing APIs are absent. Pricing is the smallest stateless piece and unblocks the next two PRs (
ce:*,cur:*).Design choices
AmazonEC2,AmazonS3,AWSLambdainus-east-1only. Keeps the image small; users wanting full coverage drop a Price List bulk download underFLOCI_SERVICES_PRICING_SNAPSHOT_PATH.ServiceCode,AttributeName,regionCode), plus root-containment check inSnapshotLoaderso the override directory can't be escaped.Test plan
./mvnw test -Dtest=PricingIntegrationTest— 26/26 green locally (Java 25)@QuarkusTestinstanceValidationException,InvalidParameterException,UnknownOperationException)AttributeNameandregionCodeScope boundaries (out of scope for this PR)
GetPriceListFileUrlreturns a stub URL).Refs: https://github.com/orgs/floci-io/discussions/791